#!/usr/bin/env python
# encoding: utf-8

# Copyright (c) 2009 Klaas Pieter Annema, Glen Hunt
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

import os
import sys

import urllib2
import base64
import re

from RTMTask import RTMTask
from icalendar import Calendar, Event
import objc, new
from datetime import date, datetime, timedelta


RTMKeychain = new.module('RTMKeychain')
objc.loadBundle('RTMKeychain', RTMKeychain.__dict__, bundle_path=os.path.join(os.path.dirname(__file__), '../RTMKeychain.framework'))
		
RTMKeychain = RTMKeychain.RTMKeychain.new()

class RTMFetcher(object):
		
	def __init__(self, username, password=None, shouldStore=False, dues=None, tags=None, priorities=None, quiet=False):
		
		self.shouldStore = shouldStore
		self.username = username
		self.password = password
		self.tasks = {}
		self.quiet = quiet
		
		if self.password == None:
			
			# Attempt to retrieve the password from the keychain
			self.password = RTMFetcher.getRememberTheMilkPassword(self.username)
		
			if self.password == None:
				if not self.quiet:
					raise AssertionError('No password given and unable to fetch it from the keychain')
				
		else:
			# Add the username and password to the keychain
			self.setRememberTheMilkAccount(self.username, self.password)
		
		
		# Fetch the ical file from the RTM server
		icalendar = self.fetchData('https://www.rememberthemilk.com/icalendar/%s' % self.username, self.username, self.password)
		
		# icalendar = Calendar.from_string(file(os.path.join(os.path.dirname(__file__), '../klapy.ics')).read())
		
		if icalendar is None:
			if not self.quiet:
				return "There was a unknown error while retrieving your tasks from Remember The Milk"
		
		
		# Iterate over all tasks and see if we want them
		for vTodo in icalendar.walk('vtodo'):						
			task = RTMTask(vTodo)
			
			# We are only interested in tasks that are not yet completed
			if not task.completed:
				
				# PARSE DUES
				if dues != None and task.due != None:
					for due in dues:
						
						if due.modifier is 'before':
							if task.due.date() < due.datetime.date():
								self.addTask(task)
							
						elif due.modifier == 'after':							
							if task.due.date() > due.datetime.date():
								self.addTask(task)
							
						else:
							if due.datetime.date() == task.due.date():
								self.addTask(task)
				
				# PARSE TAGS
				if tags is not None:
					for tag in task.tags:
						if tag in tags:
							self.addTask(task)
							
							
				# PARSE PRIORITIES
				if priorities is not None:
					for priority in priorities:
						if int(priority) == task.priority:
							self.addTask(task)
		
		dueEarlier = {}
		dueEarlierDates = []
		
		dueToday = []
		dueTomorrow = []
		dueNever = []
		
		dueLater = {}
		dueLaterDates = []

		today = datetime.today().date()
		
		# Place the tasks in their correct array
		for task in self.tasks.itervalues():
			if task.due == None:
				dueNever.append(task)
				
			else:
				if task.due.date() < today:
					
					# Use a dictionary with date as key 
					if not dueEarlier.has_key(task.due.date()):
						dueEarlier[task.due.date()] = []
						
						# Append the date to an array so we can sort this array and use the sorted values to retrieve tasks from the dictionary in order
						dueEarlierDates.append(task.due.date())
					
					dueEarlier[task.due.date()].append(task)
				
				elif task.due.date() == today:
					dueToday.append(task)
					
				elif task.due.date() == today + timedelta(days=1):
					dueTomorrow.append(task)
					
				else:
					
					if not dueLater.has_key(task.due.date()):
						dueLater[task.due.date()] = []
						dueLaterDates.append(task.due.date())
						
					dueLater[task.due.date()].append(task)
			
		# Display all the tasks with human readable dates
		
		if len(dueEarlier) > 0:
			dueEarlierDates.sort()
			
			for date in dueEarlierDates:
				
				# Check for tasks due yesterday				
				if date == today - timedelta(days=1):
					print 'YESTERDAY'
					
				else:
					# Print <NAME OF DAY>, <NAME OF MONTH> <DAY OF THE MONTH>, <YEAR>
					print date.strftime('%A, %B %d, %Y').upper()
					
				for task in dueEarlier[date]:
					print '%s' % task
					
				print
					
				
		
		if len(dueToday) > 0:
			print 'TODAY'
			
			# dueToday.sort(compare)
			dueToday.sort()
			
			for task in dueToday:
				print task
				
			print
				
		if len(dueTomorrow) > 0:
			print 'TOMORROW'
			
			# dueTomorrow.sort(compare)
			dueToday.sort()
			
			for task in dueTomorrow:
				print task
				
			print
				
		if len(dueLater) > 0:
						
			# Sort the due array by dates
			dueLaterDates.sort()
			
			for date in dueLaterDates:
				
				# Check for all tasks due this week
				if date <= today + timedelta(days=7)  and date >= today:
					
					# Print <NAME OF DAY>
					print date.strftime('%A').upper()					
						
				else:
					# Print <NAME OF DAY>, <NAME OF MONTH> <DAY OF THE MONTH>, <YEAR>
					print date.strftime('%A, %B %d, %Y').upper()
				
				# dueLaterDates is sorted so we can now use this date to get the task from the dictionary
				for task in dueLater[date]:
					print '%s' % task
				
				print
				
		if len(dueNever) > 0:
			print 'NEVER'
			
			dueNever.sort()
			
			for task in dueNever:
				print task
	
	def addTask(self, task):		
		if not self.tasks.has_key(task.uuid):
			self.tasks[task.uuid] = task
	
	
	#################################################################################
	# LOGIN
	
	@classmethod
	def getRememberTheMilkPassword(cls, username):
		# Use the objc method to retrieve the password for the given username
		return RTMKeychain.rtmPasswordForUsername_(username)
		
	def setRememberTheMilkAccount(self, username, password):
		# Use the objc method to set the keychain item
		if self.shouldStore:
			RTMKeychain.setRTMAccountForUsername_toPassword_(username, password)
	
	
	#################################################################################
	# FETCHING		
	def fetchData(self, url, username, password):
		request = urllib2.Request(url)
		
		try: 
			handle = urllib2.urlopen(request)
		except IOError, e:
			pass
		else: 
			if not self.quiet:
				print 'An error occurred while attempting to fetch data from your account, check your account name.'
			sys.exit(1)
		
		# Check if there was a error other then 401 not found
		if not hasattr(e, 'code') or e.code != 401:
			if not self.quiet:
				print 'A unknown error occurred while attempting to reach Remember The Milk. Check your internet connnection.'
			sys.exit(1)
		
		# Make sure the returned headers are formed correctly
		authline = e.headers['www-authenticate']
		authobj  = re.compile(r'''(?:\s*www-authenticate\s*:)?\s*(\w*)\s+realm=['"]([^'"]+)['"]''',
		    re.IGNORECASE)
		
		matchobj = authobj.match(authline)
		
		if not matchobj:
			if not self.quiet:
				print 'The authentication header is badly formed.'
				print authline
			sys.exit(1)
			
		scheme = matchobj.group(1)
		realm = matchobj.group(2)
		
		if scheme.lower() != 'basic':
			if not self.quiet:
				print '%s authentication is not supported' % schema.lower()
			sys.exit(1)
		
		# Encode or username password string to send it over HTTPS
		base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
		
		# Create our own header
		authheader = 'Basic %s' % base64string
		request.add_header('Authorization', authheader)
		
		# Send our request
		try:
			handle = urllib2.urlopen(request)
		except IOError, e:
			if not self.quiet:
				print "Unable to request data from your Remember The Milk account. Make sure your username and password are correct."
			sys.exit(1)
			

		# Return the requests result, this should be the icalender file
		return Calendar.from_string(handle.read())